package authentication.components;

import authentication.annotations.LoginAction;
import br.com.caelum.vraptor.Resource;
import br.com.caelum.vraptor.Result;
import br.com.caelum.vraptor.ioc.ApplicationScoped;
import br.com.caelum.vraptor.ioc.Component;
import br.com.caelum.vraptor.ioc.StereotypeHandler;
import br.com.caelum.vraptor.view.Results;
import net.vidageek.mirror.dsl.Mirror;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

@ApplicationScoped
@Component
public class LoginHandler implements StereotypeHandler {

    private Logger logger = LoggerFactory.getLogger(LoginHandler.class);

    private Class<?> classOfLoginController;
    private Method loginActionMethod;

    @Override
    public Class<? extends Annotation> stereotype() {
        return Resource.class;
    }

    @Override
    public void handle(Class<?> resource) {
        for (Method method : resource.getMethods()) {
            searchForLoginActionAndStore(resource, method);
        }
    }

    public Class<?> getClassOfController() {
        return classOfLoginController;
    }

    public Method getLoginActionMethod() {
        return loginActionMethod;
    }

    public boolean isThereALoginActionStored() {
        return classOfLoginController != null && loginActionMethod != null;
    }

    private void searchForLoginActionAndStore(Class<?> resource, Method method) {
        if (method.isAnnotationPresent(LoginAction.class)) {
            ifThereAreMoreThanOneLoginActionThrowAnException();
            storeTheLoginAction(resource, method);
            logger.debug(String.format("LoginAction annotation detected at %1$s.%2$s", classOfLoginController.getName(), loginActionMethod.getName()));
        }
    }

    private void storeTheLoginAction(Class<?> resource, Method method) {
        classOfLoginController = resource;
        loginActionMethod = method;
    }

    private void ifThereAreMoreThanOneLoginActionThrowAnException() {
        if (classOfLoginController != null && loginActionMethod != null) {
            storeTheLoginAction(null, null);
            throw new VraptorAuthenticationException("There are more than one method with the @LoginAction annotation.");
        }
    }

    public void redirectToLogin(Result result){
        LoginAction.UnauthorizedAction authFailedAction = LoginAction.UnauthorizedAction.RETURN_UNAUTHORIZED_STATUS;

        if(this.isThereALoginActionStored()){
            Method methodOfAction = this.getLoginActionMethod();
            authFailedAction = methodOfAction.getAnnotation(LoginAction.class).unauthorizedAction();
        }

        switch (authFailedAction) {
            case RETURN_UNAUTHORIZED_STATUS:
                result.use(Results.http()).sendError(401);
                break;

            case REDIRECT_TO_LOGIN:
            default:
                Object instance = result.use(Results.logic()).redirectTo(this.getClassOfController());
                new Mirror().on(instance).invoke().method(this.getLoginActionMethod()).withoutArgs();
                break;
        }
    }
}
